﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Threading;
using OdsServer.Common.DataTransfer;
using OdsServer.Common.Packet;
using OdsServer.Common.Utils;

namespace OdsClient
{
    class ClientConsole
    {
        private Socket sock;
        private int port = 10001; //TODO: move to config
        private EndPoint remoteEP;
        private Thread workThread;
        private volatile bool threadQuit = false;
        private static readonly ClientConsole client = new ClientConsole();

        public ClientConsole()
        {
            IPAddress ipv4  = DataTransferUtils.GetMachineIP();
            remoteEP        = new IPEndPoint(ipv4, this.port);

            sock = new Socket(
                    AddressFamily.InterNetwork,
                    SocketType.Dgram,
                    ProtocolType.Udp );

            sock.Blocking       = true;
            sock.DontFragment   = true;
        }

        ~ClientConsole()
        {
            if (!threadQuit)
                this.StopService(true, true);
        }

        public void Send(object packet)
        {
            if (remoteEP != null)
            {
                sock.SendTo(
                        DataTransferUtils.RawSerialize(packet),
                        Marshal.SizeOf(packet),
                        SocketFlags.None,
                        remoteEP );
            }
        }

        private void StartService()
        {
            Console.WriteLine("Service starting...");

            CommandPacket request = new CommandPacket(DataTransferCommand.Connect);

            this.Send(request);

            EndPoint serverEP   = sock.LocalEndPoint;
            byte[] data         = new byte[2048];
            IBasePacket packet  = null;

            try
            {
                // waiting for approval
                sock.ReceiveFrom(data, ref serverEP);
                packet = PacketFactory.DeserializePacket(data);
            }
            catch (SocketException)
            {
                Console.WriteLine("Server offline");
                return;
            }

            if (packet is CommandPacket)
            {
                CommandPacket response = (CommandPacket)packet;

                if (DataTransferCommand.ConnectAccepted.Equals(response.GetCommand()))
                    Console.WriteLine(string.Format("Connected to: {0}", remoteEP));
            }
            else
                throw new Exception("Server is not following the protocol");

            workThread              = new Thread(new ThreadStart(ReceiveData));
            workThread.IsBackground = true;
            workThread.Priority     = ThreadPriority.Normal;

            workThread.SetApartmentState(ApartmentState.MTA);

            workThread.Start();
        }

        private void ReceiveData()
        {
            EndPoint serverEP   = sock.LocalEndPoint;
            byte[] data         = new byte[2048];
            UInt64 packetIndex  = 0;

            while (!threadQuit)
            {
                sock.ReceiveFrom(data, ref serverEP);
                IBasePacket packet = PacketFactory.DeserializePacket(data);

                if (packet is MessagePacket)
                {
                    Console.WriteLine(((MessagePacket)packet).GetMessage());
                }
                else if (packet is SingleChannelDataPacket)
                {
                    SingleChannelDataPacket scdp = (SingleChannelDataPacket)packet;
                    Console.WriteLine(
                            string.Format("N: {2} \tT: {0} \tV: {1}", scdp.GetTimespan(), scdp.GetValue(), packetIndex));
                }
                else if (packet is MultiChannel8DataPacket)
                {
                    MultiChannel8DataPacket mc8dp   = (MultiChannel8DataPacket)packet;
                    ushort[] val                    = mc8dp.GetValues();

                    Console.WriteLine(string.Format(
                            "N: {0} T: {9} V1: {1} V2: {2} V3: {3} V4: {4} V5: {5} V6: {6} V7: {7} V8: {8}",
                            packetIndex,
                            val[0],
                            val[1],
                            val[2],
                            val[3],
                            val[4],
                            val[5],
                            val[6],
                            val[7],
                            mc8dp.GetTimespan()));
                }
                else if (packet is CommandPacket)
                {
                    CommandPacket cp = (CommandPacket)packet;
                    if (DataTransferCommand.DisconnectAcknowledged.Equals(cp.GetCommand()))
                    {
                        // time to say good bye
                        Console.WriteLine("Server sent termination signal");
                        this.StopService(true, true);
                    }
                }

                if (packetIndex == UInt64.MaxValue - 2)
                {
                    // reset counter
                    packetIndex = 0;
                }
                packetIndex++;
            }
        }

        private void StopService(bool forced, bool waitThreadStop)
        {
            if (!this.threadQuit)
            {
                if (forced)
                    Console.WriteLine("Forced service shutdown...");
                else
                    Console.WriteLine("Attempting to stop...");
                this.threadQuit = true;

                // wait till socket query gets empty
                Thread.Sleep(100);

                CommandPacket request = new CommandPacket(DataTransferCommand.Disconnect);

                this.Send(request);

                byte[] data = new byte[2048];
                IBasePacket packet;

                // no reason to notify server on forced shutdown
                if (!forced)
                {
                    // TODO: implement a way to force-close listening loop
                    do
                    {
                        sock.ReceiveFrom(data, ref remoteEP);
                        packet = PacketFactory.DeserializePacket(data);
                    } while (!((packet is CommandPacket)
                            && DataTransferCommand.DisconnectAcknowledged.Equals(((CommandPacket)packet).GetCommand())));
                }

                if (waitThreadStop)
                    workThread.Join();
            
                sock.Close();
            }
            else
                Console.WriteLine("Client socket stoped");
        }

        static void Main(string[] args)
        {
            // add exit event cleanup
            Console.CancelKeyPress += new ConsoleCancelEventHandler(Console_CancelKeyPress);

            client.StartService();

            Console.Title = client.sock.LocalEndPoint.ToString();

            while (Console.ReadKey(true).Key != ConsoleKey.Escape) 
            {
                Thread.Sleep(5);
            }

            client.StopService(false, true);
        }

        static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
        {
            client.StopService(false, true);
        }
    }
}
